home *** CD-ROM | disk | FTP | other *** search
- #include "CoreAssertion.h"
- #include "ZAMProtos.h"
- /*
- What is a sprite?
- In the context of this code a sprite is a small graphic object on the screen that has
- multiple frames, kind of like a movie. These sprites are loaded from a series of Cicn resources
- which are handy because they have a mask built right into the structure, so it is easier to
- associate the data together.
-
- So, a sprite is like a little movie, you give it a frame rate and a movement rate,
- and this sprite manager will move it automatically for you, depending on flag settings.
- With the flags you can turn off default behavior. It is also flexible in that it allows you
- to install callback routines for each animation frame drawn, and each time a sprite is moved.
- Also, each sprite has a colission handler, and when you request that colissions be checked for
- your sprite colission handler will be called when it overlaps another sprite.
-
- This is all done with built in features of the Macintosh and QuickDraw. Thanks Bill and Kon.
-
- Some of this code is hairy and messy. If I were starting this over again today, knowing what
- I do now having done it once, I would do it differently. Hopefully, since I am making this
- code available to you, you will see what I mean and be able to implement your own animation
- architecture that fits your needs and works for you.
-
- CoMation™ Architecture by Brigham Stevens
-
- Be sure to put CoMation on the box of your game or MultiMedia application if you
- are sychronizing interactive animation between multiple computers. Thats right.
-
- You can say CoMation Architecture Support right on your box, and the customers will
- clamor for more.
-
-
- Hey for more excellent examples of Macintosh animation, see SpriteWorld by Tony Myles. I
- think it is on the developer CD, perhaps in the same area as this one.
- */
-
- /* this is where all the sprites are kept */
- /* applications should keep their own references to the sprites */
- /* see TankSprites, MissileSprites, and ExplosionSprites for examples of using */
- /* most of the routines here */
-
- static spriteLayerPtr MasterSpriteHead;
- static spriteLayerPtr MasterSpriteTail;
-
- void AnimateSprites(void)
- /*
- This function is what you call once each time through your main loop.
- It will erase and draw all the sprites that are flagged for update.
- This handles the layering by doing all the necessary erasing first.
- A sprite is erased by CopyBitsing the background in over its previous location.
-
- Then the sprites are drawn in layer order. NOTE that this does NOT happen unless
- at least one sprite was erased. This may be a flaw, but it saves time.
-
- The sprites are drawn using a mask region, which has to be moved to the current position
- each time. That is what MoveCellMaskRgnToRect. A Cell is an animation cell, or frame.
- Each frame in a sprite frame set has its own mask region.
-
- There are a lot of flags used by this, so be careful. Sometimes the flags gave me grief, but
- I wanted to make it flexible. Also, lots of the flags were added in for the AppleEvent
- support.
-
- Remember, if I was going to do this again, it would be done a lot differently. But, this
- works pretty well as it is.
- */
- {
- register spritePtr spr;
- register spriteLayerPtr sprLayer;
- register frameCellPtr curFrame;
-
- register PixMapHandle srcPix;
- register PixMapHandle destPix;
-
- register Boolean needToDraw = false;
-
- /* Erase all sprites that need to be erased */
- for(sprLayer = MasterSpriteTail; sprLayer != nil; sprLayer = sprLayer->prev) {
- if( (sprLayer->layerFlags & kLayerDirty) != 0) {
- needToDraw = true;
- for(spr = sprLayer->sprites; spr != nil; spr = spr->next) {
- if( (spr->spriteFlags & kNeedsToBeErased) != 0) {
- curFrame = spr->frameList->finfo.curImage;
-
- srcPix = PreserveGraf(sprLayer->backdrop);
- destPix = PreserveGraf(sprLayer->tween);
-
- // ERASE from backdrop to the tween layer
-
- CopyBits(*srcPix,
- *destPix,
- &spr->prevBounds,
- &spr->prevBounds,
- srcCopy,
- nil);
-
- RestoreGraf();
- RestoreGraf();
-
- spr->spriteFlags &= ~kNeedsToBeErased;
- }
- }
- }
- }
-
- /* draw all sprites in layer order */
- if( needToDraw ) {
- for(sprLayer = MasterSpriteTail; sprLayer != nil; sprLayer = sprLayer->prev) {
- for(spr = sprLayer->sprites; spr != nil; spr = spr->next) {
- if(spr->visible) {
- curFrame = spr->frameList->finfo.curImage;
-
- srcPix = PreserveGraf(curFrame->image);
- destPix = PreserveGraf(sprLayer->tween);
-
- MoveCellMaskRgnToRect(curFrame, &spr->bounds);
-
- // DRAW the sprite into the offscreen tween layer
-
- CopyBits(*srcPix,
- *destPix,
- &curFrame->image->portRect,
- &spr->bounds,
- srcCopy,
- curFrame->mask);
-
- RestoreGraf();
- RestoreGraf();
-
- spr->prevBounds = spr->bounds;
- }
- }
- }
- }
-
- /* draw all sprites that need to be updated on the screen */
- if( needToDraw ) {
- for(sprLayer = MasterSpriteTail; sprLayer != nil; sprLayer = sprLayer->prev) {
- if( (sprLayer->layerFlags & kLayerDirty) != 0) {
- for(spr = sprLayer->sprites; spr != nil; spr = spr->next) {
- if(spr->spriteFlags & kNeedsToBeDrawn) {
- srcPix = PreserveGraf(sprLayer->tween);
- destPix = PreserveGraf((GWorldPtr)sprLayer->window);
-
- // DRAW the sprite to the window on screen
-
- CopyBits(*srcPix,
- *destPix,
- &spr->updateBounds,
- &spr->updateBounds,
- srcCopy,
- nil);
-
- RestoreGraf();
- RestoreGraf();
-
- spr->spriteFlags &= ~kNeedsToBeDrawn;
- }
- }
- sprLayer->layerFlags &= ~kLayerDirty;
- }
- }
- }
- }
-
- void SpriteUpdateEvent(void)
- /*
- Call this from your event loop when a window containing sprites
- has an update event. This just refreshes the screen from the
- offscreen sprite world, which should be current.
- */
- {
-
- PixMapHandle srcPix;
- PixMapHandle destPix;
-
-
- if(MasterSpriteHead) {
- srcPix = PreserveGraf(MasterSpriteHead->tween);
- destPix = PreserveGraf((GWorldPtr)MasterSpriteHead->window);
- CopyBits(*srcPix,
- *destPix,
- &MasterSpriteHead->tween->portRect,
- &MasterSpriteHead->tween->portRect,
- srcCopy,
- nil);
- RestoreGraf();
- RestoreGraf();
- }
- }
-
- void InitSprites(void)
- /*
- Call this routine very early in the program.
- It pre-allocates memory for all the sprite records as non-relocatable blocks.
- As you can see, it does not pre-allocate anything, which may slow things down
- if you are dynamically allocating memory at runtime. I changed this to simplify the
- use of Sprites, but my program pre-allocates all the sprites anyway.
- */
- {
-
- MasterSpriteHead = nil;
- MasterSpriteTail = nil;
-
- }
-
- OSErr CreateSpriteLayer(spriteLayerPtr *retSprite,
- GWorldPtr tween,
- GWorldPtr backdrop,
- WindowPtr spriteWin)
- /*
- Sprites live in layers that define which sprites overlap each other.
- Layers also make it easy to group sprites for colission detection.
- Thanks Tony for the layering concept. My first cut through this
- was not using layers, just one big gantic sprite list. Yep, gantic.
- */
- {
-
- OSErr err = noErr;
- spriteLayerPtr sl;
-
- sl = (spriteLayerPtr)NewPtrClear(sizeof(spriteLayer));
- if(!sl) {
- err = MemError();
- ErrMsgCode("\pCreateSpriteLayer NewPtrClear failed.",err);
- }
-
- if(err == noErr) {
- sl->tween = tween;
- sl->backdrop = backdrop;
- sl->window = spriteWin;
- }
-
- /* add this layer to the layer list */
- /* layers are created in front to back order */
- if(MasterSpriteTail) {
- sl->prev = MasterSpriteTail;
- MasterSpriteTail->next = sl;
- MasterSpriteTail = sl;
- } else {
- MasterSpriteHead = MasterSpriteTail = sl;
- }
-
- *retSprite = sl;
-
- return err;
- }
-
-
- void StopSpriteAction(spritePtr spr)
- /*
- This removes the sprites from the time manager queue
- if they are currently active.
- The Time Manager seems like it removes tasks when they complete
- because it has been changed to only insert them when they are primed,
- so, this will only remove the task if the bit is set indicating that it is
- active. PrimeTime sets this bit.
- */
- {
- if( (spr->frameTask.timer.qType & TaskActiveFlag) != 0) {
- (void)RmvTime(&spr->frameTask.timer);
- }
- if( (spr->moveTask.timer.qType & TaskActiveFlag) != 0) {
- (void)RmvTime(&spr->moveTask.timer);
- }
-
- }
-
- void StopSpriteLayerAction(spriteLayerPtr sprLayer)
- /*
- Freeze an entire sprite layer.
- This does not deallocate the sprite at all.
-
- Only stops it from moving around so much.
- */
- {
- spritePtr killSprite;
-
- for(killSprite = sprLayer->sprites; killSprite != nil; killSprite = killSprite->prev)
- StopSpriteAction(killSprite);
- }
-
- void KillSprites(void)
- /*
- Perhaps a misleading name,
- because this does not deallocate either. This just stops all Layers at once.
- I guess I never wrote a sprite deallocator, because it is complicated because
- frame sets are shared. I should add a user count to the frame set so that they
- know when no one else is using it, then the deallocator could be written.
-
- However, since ZAM only loads sprites once, I don't need one, so I'm not writing it.
- */
- {
- spriteLayerPtr killLayer;
-
- for(killLayer = MasterSpriteTail; killLayer != nil; killLayer = killLayer->prev)
- StopSpriteLayerAction(killLayer);
- }
-
-
- void AddSpriteToLayer(spritePtr spr, spriteLayerPtr sprLayer)
- /*
- Yes, this will take the sprite and make it a member of the layer.
- */
- {
-
- spr->next = sprLayer->sprites;
- sprLayer->sprites->prev = spr->next;
- sprLayer->sprites = spr;
- }
-
- void RemoveSpriteFromLayer(spritePtr spr, spriteLayerPtr sprLayer)
- {
- if(spr->next) {
- spr->next->prev = spr->prev;
- }
-
- if(spr->prev) {
- spr->prev->next = spr->next;
- }
-
- spr->prev = nil;
- spr->next = nil;
-
- }
-
- void MoveCellMaskRgnToRect(frameCellPtr curFrame, Rect *r)
- /*
- Offset a region to move it with the sprite.
- The maskLoc is the original topLeft of the region, and
- it is needed to preserve the region position within the sprite rectangle
- */
- {
- Point rgnOffset;
-
- /* translate the region position to the new image position */
- rgnOffset.h = r->left - (**curFrame->mask).rgnBBox.left
- + curFrame->maskLoc.h;
- rgnOffset.v = r->top - (**curFrame->mask).rgnBBox.top
- + curFrame->maskLoc.v;
- OffsetRgn(curFrame->mask, rgnOffset.h, rgnOffset.v);
- }
-
-
- OSErr CreateEmptySprite(spriteLayerPtr sprLayer,
- spritePtr *newSprite, /* new sprite returned here */
- long spriteFlags,
- long moveTimeInterval,
- long frameTimeInterval,
- long refCon) /* application value */
- /*
- Create a new sprite from parameters specified with no frame set.
- To add a frame set, either use CreateEmptyFrameSet, and then SetSpriteFrameSet
- to copy a frame set to it.
-
- Some things about this are bad. FrameSet headers are block moved around
- when the frame sets are shared. This is not good, but it is not that much memory.
- See SpriteFrameSet.c for more info on frame sets.
- */
- {
- spritePtr tSprite;
- OSErr err = noErr;
-
- tSprite = (spritePtr)NewPtrClear(sizeof(sprite));
-
- if(tSprite == nil) {
- err = paramErr;
- ErrMsg("\pNo More Sprites may be allocated. ••••EXITING••••.");
- } else {
- tSprite->usrNext = nil;
- tSprite->usrPrev = nil;
- tSprite->moveHandler = nil;
- tSprite->visible = false;
- tSprite->loc.h = 0;
- tSprite->loc.v = 0;
- tSprite->vel.h = 0;
- tSprite->vel.v = 0;
- tSprite->frameList = nil;
- tSprite->refCon = refCon;
- tSprite->spriteFlags = spriteFlags;
- tSprite->inUse = false;
- tSprite->moveTimeInterval = moveTimeInterval;
- tSprite->frameTimeInterval = frameTimeInterval;
- tSprite->ownerLayer = sprLayer;
- *newSprite = tSprite;
- AddSpriteToLayer(tSprite, sprLayer);
-
- }
-
- return err;
- }
-
- OSErr CreateColorIconSprite(spriteLayerPtr sprLayer,
- spritePtr *newSprite, /* new sprite returned here */
- short startID, /* starting resource ID of cicn */
- short numFrames, /* number of resources to load */
- long spriteFlags,
- long moveTimeInterval,
- long frameTimeInterval,
- long refCon) /* application value */
-
- /*
- Create a new sprite from parameters specified
- starting location for all sprites is 0,0
- velocity is 0,0
- bounds taken from icon dimensions - GWORLD of first frame
- assumes that all icons have the same dimension
- center - calcd from bounds
- dimension - h width v = height calcd from bounds
- frameList - built from color icons starting from startID
- visible - set to false
-
- Sprites created with this can then be copied. See MissileSprites.c (LoadMIssileSprites)
- for an example, or ExplosionSprites.c.
- */
- {
- spritePtr tSprite;
- OSErr err;
-
- /* create an empty sprite first */
- err = CreateEmptySprite(sprLayer,
- &tSprite, /* new sprite returned here */
- spriteFlags,
- moveTimeInterval,
- frameTimeInterval,
- refCon); /* application value */
-
- /* now create the frameset list, and attach it to the sprite */
-
- if(err == noErr) {
- err = CreateColorIconFrameSet(&tSprite->frameList, startID, numFrames);
- if(err != noErr) {
- ErrMsgCode("\pError in CreateColorIconFrameSet!",err);
- } else {
- *newSprite = tSprite;
- }
- }
-
- return err;
- }
-
- void SetSpriteLoc(spritePtr spr, Fixed h, Fixed v)
- /*
- Change the position of the sprite on the screen
- */
- {
- Boolean showIt = false;
-
- if(spr->visible) {
- showIt = true;
- HideSprite(spr);
- }
-
- spr->prevBounds = spr->bounds;
-
- spr->loc.h = h;
- spr->loc.v = v;
-
- spr->bounds.top = FixToInt(spr->loc.v) - spr->frameList->finfo.center.v;
- spr->bounds.left = FixToInt(spr->loc.h) - spr->frameList->finfo.center.v;
- spr->bounds.bottom = spr->bounds.top + spr->frameList->finfo.dimension.v;
- spr->bounds.right = spr->bounds.left + spr->frameList->finfo.dimension.h;
-
- MyUnionRect(&spr->prevBounds, &spr->bounds, &spr->updateBounds);
- spr->spriteFlags |= kNeedsToBeErased | kNeedsToBeDrawn;
-
- if(showIt)
- ShowSprite(spr);
-
- }
-
- void ShowSprite(spritePtr spr)
- /*
- Make the sprite visibile.
- */
- {
- if(!spr->visible) {
- spr->visible = true;
- spr->spriteFlags |= kNeedsToBeDrawn;
- }
- }
-
- void HideSprite(spritePtr spr)
- /*
- hide the sprite on the screen
- and draw it.
- In this case if the sprite was not ever
- previously drawn, the drawing routine will
- not draw anything.
- */
- {
- if(spr->visible) {
- spr->visible = false;
- spr->spriteFlags |= kNeedsToBeErased;
- }
- }
-
-
- void StartSpriteAction(spritePtr spr)
- /*
- This launches the XThing tasks for updating the sprites, which in turn
- uses the Time Manger. XThings are periodical tasks that run as close
- if any of the intervals are zero, then the task is not launched
- */
- {
- if(spr->frameTimeInterval) {
- (void)StartXThing(&spr->frameTask, spr->frameTimeInterval,
- (updateProc)SpriteFrameTask, (long)spr);
- }
-
- if(spr->moveTimeInterval) {
- (void)StartXThing(&spr->moveTask, spr->moveTimeInterval,
- (updateProc)SpriteMoveTask, (long)spr);
- }
- }
-
- void StartRemoteSpriteAction(spritePtr spr)
- /*
- Same as above, only the timer is never fired.
- Instead, they are manually updated when a network message is
- received saying to update the sprite.
- */
- {
- (void)AddXThing(&spr->frameTask, spr->frameTimeInterval,
- (updateProc)SpriteFrameTask, (long)spr);
-
- (void)AddXThing(&spr->moveTask, spr->moveTimeInterval,
- (updateProc)SpriteMoveTask, (long)spr);
- }
-
-
- Boolean SpriteFrameTask(xthing *xtp, spritePtr spr)
- /*
- This is the XThing task that changes the frame of the sprite.
-
- If the kFrameTaskBeforeUpdate bit it set in spriteFlags and if there
- is a global frame task installed, then it will be called before
- the frame of the sprite is changed.
-
- If the kDefaultFrameAdvance bit is set, then the task
- will go ahead and advance the frame on to the next one.
-
- if the kFrameTaskAfterUpdate bit is set, then the task will
- also call the global frame task after the frame is advanced.
-
- Finally, if the current frame has a callback set up, then it will be called.
-
- All of the callbacks return a boolean that determines if this task is to be rescheduled.
- */
- {
- register frameCellPtr curFrame;
- register frameSetPtr frameset;
- register long frameDelay;
- register Boolean reTimeTask = true;
-
- frameset = spr->frameList;
-
- if( (spr->spriteFlags & kFrameTaskBeforeUpdate) != 0) {
- if(spr->frameHandler) {
- reTimeTask = (*spr->frameHandler)(spr, nil);
- }
- }
-
- if( (spr->spriteFlags & kDefaultFrameAdvance) != 0) {
- frameset->finfo.frameIndex++;
-
- if(frameset->finfo.frameIndex >= frameset->finfo.frameCount) {
- frameset->finfo.frameIndex = 0;
- }
-
- curFrame = &frameset->flist[frameset->finfo.frameIndex];
- frameset->finfo.prevImage = frameset->finfo.curImage;
- frameset->finfo.curImage = curFrame;
- spr->updateBounds = spr->bounds;
- spr->spriteFlags |= kNeedsToBeDrawn | kNeedsToBeErased;
- spr->ownerLayer->layerFlags |= kLayerDirty;
- }
-
- if( (spr->spriteFlags & kFrameTaskAfterUpdate) != 0) {
- if(spr->frameHandler) {
- reTimeTask = (*spr->frameHandler)(spr, (struct frameCell *)-1);
- }
- }
-
- /* check if this frame has a callback and call it */
- curFrame = &frameset->flist[frameset->finfo.frameIndex];
- if(curFrame->frameCB) {
- reTimeTask = (*curFrame->frameCB)(spr, (struct frameCell *)curFrame);
- }
-
- if( (spr->spriteFlags & kRemoteSprite) != 0)
- reTimeTask = false;
-
- return reTimeTask;
-
- }
-
-
- Boolean SpriteMoveTask(xthing *xtp, spritePtr spr)
- /*
- This procedure moves the sprites in the default way,
- applying the velocity to the location, and then recalculating the
- bounds based on this position.
-
- This procedure changes fields that DrawSprite depends upon:
-
- updateBounds covers the total area of the screen that needs to be changed,
- incorporating the previous position and the current position.
-
- prevBounds is the position the sprite WAS in. This is used for erasing the
- previous image.
-
- prevImage is the previous frameCell the sprite was drawn with. This is used to
- erase the previous image.
-
- */
- {
-
- moveProc moveJSR;
- Boolean result = true;
- short adjustPos;
- Boolean reAdjustNecessary;
-
- if( (spr->vel.h != 0) || (spr->vel.v != 0) ) {
- spr->prevBounds = spr->bounds; /* save previous location */
-
- /* the sprite location is the center of the sprite */
- if(spr->spriteFlags & kRemoteUpdate) {
- spr->loc = spr->remoteLoc; /* slam the sprite */
- } else {
- spr->loc.h += spr->vel.h; /* offset the sprite */
- spr->loc.v += spr->vel.v;
- }
-
- /* build the sprite integer rectangle location from the fixed center point */
- spr->bounds.top = FixToInt(spr->loc.v) - spr->frameList->finfo.center.v;
- spr->bounds.left = FixToInt(spr->loc.h) - spr->frameList->finfo.center.v;
- spr->bounds.bottom = spr->bounds.top + spr->frameList->finfo.dimension.v;
- spr->bounds.right = spr->bounds.left + spr->frameList->finfo.dimension.h;
-
- /* calculate the entire area that needs updating */
- MyUnionRect(&spr->bounds, &spr->prevBounds, &spr->updateBounds);
-
- spr->frameList->finfo.prevImage = spr->frameList->finfo.curImage;
-
- /* set the update flag if we have moved a whole pixel at least */
- if(spr->prevBounds.top != spr->bounds.top) {
- spr->spriteFlags |= kNeedsToBeDrawn | kNeedsToBeErased;
- spr->ownerLayer->layerFlags |= kLayerDirty;
- }
- else if(spr->prevBounds.left != spr->bounds.left) {
- spr->spriteFlags |= kNeedsToBeDrawn | kNeedsToBeErased;
- spr->ownerLayer->layerFlags |= kLayerDirty;
- }
-
- /* this constrains the sprite to within the constrain rectangle */
- if(spr->spriteFlags & kConstrainToRect) {
- reAdjustNecessary = false;
- if(spr->bounds.right > spr->constrainRect.right) {
- adjustPos = spr->bounds.right - spr->constrainRect.right;
- spr->loc.h -= ff(adjustPos);
- reAdjustNecessary = true;
- } else if(spr->bounds.left < spr->constrainRect.left) {
- adjustPos = spr->constrainRect.left - spr->bounds.left;
- spr->loc.h += ff(adjustPos);
- reAdjustNecessary = true;
- }
-
- if(spr->bounds.bottom > spr->constrainRect.bottom) {
- adjustPos = spr->bounds.bottom - spr->constrainRect.bottom;
- spr->loc.v -= ff(adjustPos);
- reAdjustNecessary = true;
- } else if(spr->bounds.top < spr->constrainRect.top) {
- adjustPos = spr->constrainRect.top - spr->bounds.top;
- spr->loc.v += ff(adjustPos);
- reAdjustNecessary = true;
- }
- if(reAdjustNecessary) {
- spr->bounds.top = FixToInt(spr->loc.v) - spr->frameList->finfo.center.v;
- spr->bounds.left = FixToInt(spr->loc.h) - spr->frameList->finfo.center.v;
- spr->bounds.bottom = spr->bounds.top + spr->frameList->finfo.dimension.v;
- spr->bounds.right = spr->bounds.left + spr->frameList->finfo.dimension.h;
- }
- }
-
- }
-
- /* check if there is a move callback */
- if(spr->moveHandler) {
- moveJSR = (moveProc)spr->moveHandler;
- result = (*moveJSR)(spr);
- }
-
- /* remote sprites are not timed by the time manager. They are moved when an update is rcvd */
- if( (spr->spriteFlags & kRemoteSprite) != 0)
- result = false;
-
- /* tell XThing if we want to be added back as time manager task */
- return result;
- }